BemÀstra avancerade TypeScript-funktioner: villkorsstyrda typer, mall-literaler och strÀngmanipulation. Bygg robusta, typsÀkra API:er. En guide för globala utvecklare.
LÄs upp TypeScript's fulla potential: En djupdykning i villkorsstyrda typer, mall-literaler och avancerad strÀngmanipulation
Inom modern mjukvaruutveckling har TypeScript utvecklats lÄngt bortom sin ursprungliga roll som en enkel typskontroll för JavaScript. Det har blivit ett sofistikerat verktyg för vad som kan beskrivas som typnivÄprogrammering. Detta paradigm tillÄter utvecklare att skriva kod som opererar pÄ typerna sjÀlva, vilket skapar dynamiska, sjÀlv-dokumenterande och anmÀrkningsvÀrt sÀkra API:er. KÀrnan i denna revolution Àr tre kraftfulla funktioner som arbetar tillsammans: Villkorsstyrda Typer, Mall-Literal-Typer och en svit av inbyggda StrÀngmanipulations-Typer.
För utvecklare vĂ€rlden över som vill höja sina TypeScript-kunskaper Ă€r förstĂ„elsen för dessa koncept inte lĂ€ngre en lyx â det Ă€r en nödvĂ€ndighet för att bygga skalbara och underhĂ„llbara applikationer. Denna guide tar dig med pĂ„ en djupdykning, med start frĂ„n grundlĂ€ggande principer och byggandes upp till komplexa, verkliga mönster som demonstrerar deras kombinerade kraft. Oavsett om du bygger ett designsystem, en typsĂ€ker API-klient eller ett komplext datahanteringsbibliotek, kommer bemĂ€stringen av dessa funktioner fundamentalt att förĂ€ndra hur du skriver TypeScript.
Grunderna: Villkorsstyrda Typer (TernÀr-operatorn `extends`)
I grunden tillÄter en villkorsstyrd typ dig att vÀlja en av tvÄ möjliga typer baserat pÄ en typrelationskontroll. Om du Àr bekant med JavaScripts ternÀra operator (condition ? valueIfTrue : valueIfFalse), kommer du att finna syntaxen omedelbart intuitiv:
type Result = SomeType extends OtherType ? TrueType : FalseType;
HÀr fungerar nyckelordet extends som vÄrt villkor. Det kontrollerar om SomeType Àr tilldelningsbart till OtherType. LÄt oss bryta ner det med ett enkelt exempel.
GrundlÀggande exempel: Kontrollera en typ
FörestÀll dig att vi vill skapa en typ som resulterar i true om en given typ T Àr en strÀng, och false annars.
type IsString
Vi kan sedan anvÀnda denna typ sÄ hÀr:
type A = IsString<"hello">; // typ A Àr true
type B = IsString<123>; // typ B Àr false
Detta Àr den grundlÀggande byggstenen. Men den verkliga kraften hos villkorsstyrda typer slÀpps lös nÀr de kombineras med nyckelordet infer.
Kraften i `infer`: Extrahera typer inifrÄn
Nyckelordet infer Àr en game-changer. Det lÄter dig deklarera en ny generisk typvariabel inom extends-klausulen, vilket effektivt fÄngar en del av typen du kontrollerar. TÀnk pÄ det som en typnivÄ-variabeldeklaration som fÄr sitt vÀrde frÄn mönstermatchning.
Ett klassiskt exempel Àr att veckla ut typen som finns i en Promise.
type UnwrapPromise
LÄt oss analysera detta:
T extends Promise: Detta kontrollerar omTÀr enPromise. Om det Àr det, försöker TypeScript matcha strukturen.infer U: Om matchningen lyckas, fÄngar TypeScript typen somPromiselöser till och lÀgger den i en ny typvariabel vid namnU.? U : T: Om villkoret Àr sant (Tvar enPromise), Àr den resulterande typenU(den uppvecklade typen). Annars Àr den resulterande typen bara den ursprungliga typenT.
AnvÀndning:
type User = { id: number; name: string; };
type UserPromise = Promise
type UnwrappedUser = UnwrapPromise
type UnwrappedNumber = UnwrapPromise
Detta mönster Àr sÄ vanligt att TypeScript inkluderar inbyggda hjÀlp-typer som ReturnType, vilken implementeras med samma princip för att extrahera returtypen för en funktion.
Distributiva villkorsstyrda typer: Arbeta med unioner
Ett fascinerande och avgörande beteende hos villkorsstyrda typer Àr att de blir distributiva nÀr typen som kontrolleras Àr en "naken" generisk typparameter. Detta innebÀr att om du skickar en unionstyp till den, kommer villkoret att appliceras pÄ varje medlem av unionen individuellt, och resultaten kommer att samlas tillbaka till en ny union.
ĂvervĂ€g en typ som konverterar en typ till en array av den typen:
type ToArray
Om vi skickar en unionstyp till ToArray:
type StrOrNumArray = ToArray
Resultatet Àr inte (string | number)[]. Eftersom T Àr en naken typparameter distribueras villkoret:
ToArrayblirstring[]ToArrayblirnumber[]
Det slutliga resultatet Àr unionen av dessa individuella resultat: string[] | number[].
Denna distributiva egenskap Àr otroligt anvÀndbar för att filtrera unioner. Till exempel anvÀnder den inbyggda hjÀlp-typen Extract detta för att vÀlja medlemmar frÄn union T som Àr tilldelningsbara till U.
Om du behöver förhindra detta distributiva beteende kan du linda in typparametern i en tupel pÄ bÄda sidor av extends-klausulen:
type ToArrayNonDistributive
type StrOrNumArrayUnified = ToArrayNonDistributive
Med denna solida grund, lÄt oss utforska hur vi kan konstruera dynamiska strÀngtyper.
Bygga dynamiska strÀngar pÄ typnivÄ: Mall-literal-typer
Introducerade i TypeScript 4.1, Mall-literal-typer lÄter dig definiera typer som Àr formade som JavaScripts mall-literal-strÀngar. De gör det möjligt för dig att sammanfoga, kombinera och generera nya strÀngliteral-typer frÄn befintliga.
Syntaxen Àr precis vad du kan förvÀnta dig:
type World = "World";
type Greeting = `Hello, ${World}!`; // typ Greeting Àr "Hello, World!"
Detta kan tyckas enkelt, men dess kraft ligger i att kombinera det med unioner och generiska typer.
Unioner och permutationer
NÀr en mall-literal-typ involverar en union, expanderar den till en ny union som innehÄller varje möjlig strÀngpermutation. Detta Àr ett kraftfullt sÀtt att generera en uppsÀttning vÀldefinierade konstanter.
FörestÀll dig att definiera en uppsÀttning CSS-marginalegenskaper:
type Side = "top" | "right" | "bottom" | "left";
type MarginProperty = `margin-${Side}`;
Den resulterande typen för MarginProperty Àr:
"margin-top" | "margin-right" | "margin-bottom" | "margin-left"
Detta Àr perfekt för att skapa typsÀkra komponent-props eller funktionsargument dÀr endast specifika strÀngformat Àr tillÄtna.
Kombinera med generiska typer
Mall-literaler lyser verkligen nÀr de anvÀnds med generiska typer. Du kan skapa fabrikstyper som genererar nya strÀngliteral-typer baserade pÄ nÄgon inmatning.
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Detta mönster Àr nyckeln till att skapa dynamiska, typsÀkra API:er. Men vad hÀnder om vi behöver Àndra strÀngens skiftlÀge, som att Àndra "user" till "User" för att fÄ "onUserChange"? Det Àr dÀr strÀngmanipulations-typer kommer in.
VerktygslÄdan: Inbyggda strÀngmanipulations-typer
För att göra mall-literaler Ànnu kraftfullare tillhandahÄller TypeScript en uppsÀttning inbyggda typer för att manipulera strÀngliteraler. Dessa Àr som hjÀlpfunktioner men för typsystemet.
SkiftlÀgesmodifierare: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize`
Dessa fyra typer gör precis vad deras namn antyder:
Uppercase: Konverterar hela strÀngtypen till versaler.type LOUD = Uppercase<"hello">; // "HELLO"Lowercase: Konverterar hela strÀngtypen till gemener.type quiet = Lowercase<"WORLD">; // "world"Capitalize: Konverterar det första tecknet i strÀngtypen till versaler.type Proper = Capitalize<"john">; // "John"Uncapitalize: Konverterar det första tecknet i strÀngtypen till gemener.type variable = Uncapitalize<"PersonName">; // "personName"
LÄt oss Äterbesöka vÄrt tidigare exempel och förbÀttra det med hjÀlp av Capitalize för att generera konventionella namn pÄ hÀndelsehanterare:
type MakeEventListener
type UserListener = MakeEventListener<"user">; // "onUserChange"
type ProductListener = MakeEventListener<"product">; // "onProductChange"
Nu har vi alla bitar pÄ plats. LÄt oss se hur de kombineras för att lösa komplexa, verkliga problem.
Syntesen: Kombinera alla tre för avancerade mönster
Det Àr hÀr teori möter praktik. Genom att sammanfoga villkorsstyrda typer, mall-literaler och strÀngmanipulation kan vi bygga otroligt sofistikerade och sÀkra typdefinitioner.
Mönster 1: Den helt typsÀkra hÀndelseutgivaren (Event Emitter)
MÄl: Skapa en generisk EventEmitter-klass med metoder som on(), off() och emit() som Àr helt typsÀkra. Detta innebÀr:
- HÀndelsenamnet som skickas till metoderna mÄste vara en giltig hÀndelse.
- Nyttolasten (payload) som skickas till
emit()mÄste matcha den typ som definierats för den hÀndelsen. - Callback-funktionen som skickas till
on()mÄste acceptera den korrekta nyttolast-typen för den hÀndelsen.
Först definierar vi en karta över hÀndelsenamn till deras nyttolast-typer:
interface EventMap {
"user:created": { userId: number; name: string; };
"user:deleted": { userId: number; };
"product:added": { productId: string; price: number; };
}
Nu kan vi bygga den generiska EventEmitter-klassen. Vi kommer att anvÀnda en generisk parameter Events som mÄste utöka vÄr EventMap-struktur.
class TypedEventEmitter
LÄt oss instansiera och anvÀnda den:
const appEvents = new TypedEventEmitter
// Detta Àr typsÀkert. Nyttolasten Àr korrekt infererad som { userId: number; name: string; }
appEvents.on("user:created", (payload) => {
console.log(`User created: ${payload.name} (ID: ${payload.userId})`);
});
// TypeScript kommer att ge fel hÀr eftersom "user:updated" inte Àr en nyckel i EventMap
// appEvents.on("user:updated", () => {}); // Fel!
// TypeScript kommer att ge fel hÀr eftersom nyttolasten saknar egenskapen 'name'
// appEvents.emit("user:created", { userId: 123 }); // Fel!
Detta mönster ger kompileringstids-sÀkerhet för vad som traditionellt Àr en mycket dynamisk och felbenÀgen del av mÄnga applikationer.
Mönster 2: TypsÀker sökvÀgsÄtkomst för kapslade objekt
MÄl: Skapa en hjÀlp-typ, PathValue, som kan bestÀmma typen av ett vÀrde i ett kapslat objekt T med hjÀlp av en strÀngsökvÀg i punktnotation P (t.ex. "user.address.city").
Detta Àr ett mycket avancerat mönster som visar rekursiva villkorsstyrda typer.
HÀr Àr implementeringen, som vi kommer att bryta ner:
type PathValue
? Key extends keyof T
? PathValue
: never
: P extends keyof T
? T[P]
: never;
LÄt oss spÄra dess logik med ett exempel: PathValue
- Initialt anrop:
PÀr"a.b.c". Detta matchar mall-literalen`${infer Key}.${infer Rest}`. Keyinfereras som"a".Restinfereras som"b.c".- Första rekursionen: Typen kontrollerar om
"a"Àr en nyckel tillMyObject. Om ja, anropar den rekursivtPathValue. - Andra rekursionen: Nu Àr
P"b.c". Den matchar mall-literalen igen. Keyinfereras som"b".Restinfereras som"c".- Typen kontrollerar om
"b"Àr en nyckel tillMyObject["a"]och anropar rekursivtPathValue. - Basfall: Slutligen Àr
P"c". Detta matchar inte`${infer Key}.${infer Rest}`. Typlogiken faller igenom till den andra villkorliga satsen:P extends keyof T ? T[P] : never. - Typen kontrollerar om
"c"Àr en nyckel tillMyObject["a"]["b"]. Om ja, Àr resultatetMyObject["a"]["b"]["c"]. Om inte, Àr detnever.
AnvÀndning med en hjÀlpfunktion:
declare function get
const myObject = {
user: {
name: "Alice",
address: {
city: "Wonderland",
zip: 12345
}
}
};
const city = get(myObject, "user.address.city"); // const city: string
const zip = get(myObject, "user.address.zip"); // const zip: number
const invalid = get(myObject, "user.email"); // const invalid: never
Denna kraftfulla typ förhindrar körningsfel frÄn stavfel i sökvÀgar och ger perfekt typinferens för djupt kapslade datastrukturer, en vanlig utmaning i globala applikationer som hanterar komplexa API-svar.
BÀsta praxis och prestandaövervÀganden
Som med alla kraftfulla verktyg Àr det viktigt att anvÀnda dessa funktioner klokt.
- Prioritera lÀsbarhet: Komplexa typer kan snabbt bli olÀsliga. Bryt ner dem i mindre, vÀl-namngivna hjÀlptyper. AnvÀnd kommentarer för att förklara logiken, precis som du skulle göra med komplex körningskod.
- FörstÄ typen `never`: Typen
neverÀr ditt primÀra verktyg för att hantera feltillstÄnd och filtrera unioner i villkorsstyrda typer. Den representerar ett tillstÄnd som aldrig ska intrÀffa. - Akta dig för rekursionsgrÀnser: TypeScript har en rekursionsdjupgrÀns för typinstansiering. Om dina typer Àr för djupt kapslade eller oÀndligt rekursiva, kommer kompilatorn att ge fel. Se till att dina rekursiva typer har ett tydligt basfall.
- Ăvervaka IDE-prestanda: Extremt komplexa typer kan ibland pĂ„verka prestandan hos TypeScript-sprĂ„kservern, vilket leder till lĂ„ngsammare automatisk komplettering och typskontroll i din editor. Om du upplever lĂ„ngsamma svar, se om en komplex typ kan förenklas eller brytas ner.
- Veta nÀr man ska sluta: Dessa funktioner Àr till för att lösa komplexa problem med typsÀkerhet och utvecklarupplevelse. AnvÀnd dem inte för att överkomplicera enkla typer. MÄlet Àr att öka tydligheten och sÀkerheten, inte att lÀgga till onödig komplexitet.
Slutsats
Villkorsstyrda typer, mall-literaler och strÀngmanipulations-typer Àr inte bara isolerade funktioner; de Àr ett tÀtt integrerat system för att utföra sofistikerad logik pÄ typnivÄ. De ger oss möjlighet att gÄ bortom enkla annoteringar och bygga system som Àr djupt medvetna om sin egen struktur och sina begrÀnsningar.
Genom att bemÀstra denna trio kan du:
- Skapa sjÀlv-dokumenterande API:er: Typerna sjÀlva blir dokumentationen, som vÀgleder utvecklare att anvÀnda dem korrekt.
- Eliminera hela klasser av buggar: Typfel fÄngas vid kompileringstid, inte av anvÀndare i produktion.
- FörbÀttra utvecklarupplevelsen: Njut av rik automatisk komplettering och inbyggda felmeddelanden för Àven de mest dynamiska delarna av din kodbas.
Att omfamna dessa avancerade funktioner förvandlar TypeScript frÄn ett sÀkerhetsnÀt till en kraftfull partner i utvecklingen. Det tillÄter dig att koda komplex affÀrslogik och invarianter direkt in i typsystemet, vilket sÀkerstÀller att dina applikationer blir mer robusta, underhÄllbara och skalbara för en global publik.